local lpack = require"pack"
require"common"
module(...,package.seeall)

local slen,sbyte,ssub,sgsub,schar,srep,smatch,sgmatch = string.len,string.byte,string.sub,string.gsub,string.char,string.rep,string.match,string.gmatch

HEART,LBS3,GPSLBSWIFIEXT,RPTPARA,SETPARARSP,DEVEVENTRSP,INFO,GETPARA = 4,5,8,10,11,12,14,15
QRYPOS,SOSPOS = 1,3
EGG = 0
UPDPB,UPDPBRSP,CHATRCD,IMG_RPT = 0x10,0x11,0x15,0x1E
RPTFREQ,CALLVOL,SILTIME,FIXMOD,AWARD,NOTICE,LOG,WHITENUMSWITCH,DIALSWITCH = 3,16,20,23,41,42,0x2F,0x36,0x3A
SETPARA,QRYLOC,RESET,MONIT,POWEROFF,RESTORE,RINGON,SMPSET,PARASYNC,GETPARARSP,QRYPARA,TAKEPHOTO = 10,0x32,0x33,0x34,0x35,0x36,0x3B,0x3C,0x3E,0x40,0x50,0x58
DATETIME,SMSCENTERNUM,SIMNUMRPT = 0,4,5
VER,DEVPARA = 0,2

local PROTOVERSION = 0
local serial = 0
local imei 
local get


function bcd(d,n)
	local l = slen(d or "")
	local num
	local t = {}

	for i=1,l,2 do
		num = tonumber(ssub(d,i,i+1),16)

		if i == l then
			num = 0xf0+num
		else
			num = (num%0x10)*0x10 + num/0x10
		end

		table.insert(t,num)
	end

	local s = schar(_G.unpack(t))

	l = slen(s)

	if l < n then
		s = s .. srep("\255",n-l)
	elseif l > n then
		s = ssub(s,1,n)
	end

	return s
end

local function unbcd(d)
	local byte,v1,v2
	local t = {}

	for i=1,slen(d) do
		byte = sbyte(d,i)
		v1,v2 = bit.band(byte,0x0f),bit.band(bit.rshift(byte,4),0x0f)

		if v1 == 0x0f then break end
		table.insert(t,v1)

		if v2 == 0x0f then break end
		table.insert(t,v2)
	end

	return table.concat(t)
end

local function unbcdnum(s)
	local tnum,idx,i = {},1
	for i=1,slen(s) do
		if ssub(s,i,i)=="\255" then
			idx = idx+1
		else
			tnum[idx] = (tnum[idx] or "")..ssub(s,i,i)
		end
	end
	
	local tnewnum,newidx = {},1
	for i=1,idx do
		if tnum[i] and tnum[i]~="" then
			tnewnum[newidx] = unbcd(tnum[i])
			newidx = newidx+1
		end		
	end
	
	return tnewnum
end

local function enstat()
	local stat = get("STATUS")
	local rssi = get("RSSI")

	local extend = ""
	if stat.chgState then extend = lpack.pack(">bHb",6,1,stat.chgState) end

	return lpack.pack(">bbHA",stat.charger*2,(rssi > 31 and 31 or rssi),stat.volt,extend)
end

local function encellinfoext()
	local info,ret,t,mcc,mnc,lac,ci,rssi,k,v,m,n,cntrssi = get("CELLINFOEXT"),"",{}
	log.info("protoair.encellinfoext",info)
	for mcc,mnc,lac,ci,rssi in sgmatch(info,"(%d+)%.(%d+)%.(%d+)%.(%d+)%.(%d+);") do
		mcc,mnc,lac,ci,rssi = tonumber(mcc),tonumber(mnc),tonumber(lac),tonumber(ci),(tonumber(rssi) > 31) and 31 or tonumber(rssi)
		local handle = nil
		for k,v in pairs(t) do
			if v.lac == lac and v.mcc == mcc and v.mnc == mnc then
				if #v.rssici < 8 then
					table.insert(v.rssici,{rssi=rssi,ci=ci})
				end
				handle = true
				break
			end
		end
		if not handle then
			table.insert(t,{mcc=mcc,mnc=mnc,lac=lac,rssici={{rssi=rssi,ci=ci}}})
		end
	end
	for k,v in pairs(t) do
		ret = ret .. lpack.pack(">HHb",v.lac,v.mcc,v.mnc)
		for m,n in pairs(v.rssici) do
			cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici-1) or 0),5),n.rssi)
			ret = ret .. lpack.pack(">bH",cntrssi,n.ci)
		end
	end

	return schar(#t)..ret
end

function pack(id,...)
	if not imei then imei = bcd(get("IMEI"),8) end

	local head = schar(id)

	local function lbs3()
		return lpack.pack(">AbA",encellinfoext(),get("TA"),enstat())
	end	

	local function heart()
		return lpack.pack("A",enstat())
	end	
	
	local function gpslbswifiext(typ)
		return lpack.pack(">bAAHbAbAA",typ,string.fromhex("FFFFFFFFFF"),string.fromhex("FFFFFFFFFF"),0,0,encellinfoext(),get("TA"),string.fromhex("00"),enstat())
	end
	
	local function rptpara(dat)
		return dat or ""
	end
	
	local function rsp(typ,result)
		return lpack.pack("bA",typ,result)
	end
	
	local function info(ver)
		return lpack.pack(">bHHbHHbHAbHibHAbHA",0,2,get("PROJECTID"),1,2,get("HEART"),2,2,bcd(sgsub(get("VERSION"),"%.",""),2),6,4,ver or get("PARAVER"),8,slen(get("PROJECT")),get("PROJECT"),4,slen(get("ICCID")),get("ICCID"))
	end
	
	local function getpara(t)
		return schar(t)
	end
	
	local function empty()
		return ""
	end
	
	local function chatrcd(s,t,c,typ,tmlen,d)
		return lpack.pack(">ibbbbbA",0,s,t,c,typ,tmlen,d)
	end	

	local function imgRpt(cmdTyp,s,t,c,typ,d)
		return lpack.pack(">bbbbbA",cmdTyp,s,t,c,typ,d)
	end
	
	local procer = {
		[LBS3] = lbs3,
		[HEART] = heart,
		[GPSLBSWIFIEXT] = gpslbswifiext,
		[RPTPARA] = rptpara,
		[SETPARARSP] = rsp,
		[DEVEVENTRSP] = rsp,
		[INFO] = info,
		[GETPARA] = getpara,
		[CHATRCD] = chatrcd,
		[IMG_RPT] = imgRpt,
		[UPDPBRSP] = rsp,
	}	

	local s = procer[id](...)
	s = head..s
	log.info("protoair.pack",id,string.tohex(ssub(s,1,200)))
	return s
end

local rcvpacket = {}

function unpack(s)
	rcvpacket = {}	
	
	local function setpara(d)
		if slen(d) > 0 then			
			
			local function unsignshort(m)
				if slen(m) ~= 2 then return end
				_,rcvpacket.val = lpack.unpack(m,">H")
				return true
			end
			
			local function empty(m)
				return m==""
			end
			
			local function numlist(m)
				if m == "" then return end
				rcvpacket.val = {}
				local i = 1
				while i < slen(m) do
					if i+1 > slen(m) then return end
					if i+1+sbyte(m,i+1) > slen(m) then return end
					rcvpacket.val[sbyte(m,i)] = (sbyte(m,i+1)==0 and "" or unbcd(ssub(m,i+2,i+1+sbyte(m,i+1))))					
					i = i+2+sbyte(m,i+1)
				end
				return true
			end
			
			local function unsignchar(m)
				if slen(m) ~= 1 then return end
				rcvpacket.val = sbyte(m)
				return true
			end
			
			local function listunsignchar(m)
				if m == "" then return end
				rcvpacket.val = {}
				local i = 1
				while i < slen(m) do
					if i+1 > slen(m) then return end					
					rcvpacket.val[sbyte(m,i)] = sbyte(m,i+1)
					i = i+2					
				end
				return true
			end
			
			local function timesect(m)
				if m == "" then return end
				rcvpacket.val = {}
				local i,tmp,j = 1
				while i < slen(m) do
					if i+5 > slen(m) then return end
					local flg = sbyte(m,i+1)
					tmp = ""
					for j=1,7 do
						tmp = tmp..(bit.band(flg,2^j)==0 and "0" or "1")
					end
					tmp = tmp.."!"..string.format("%02d%02d%02d%02d",sbyte(m,i+2),sbyte(m,i+3),sbyte(m,i+4),sbyte(m,i+5))
					rcvpacket.val[sbyte(m,i)] = tmp
					i = i+6					
				end
				return true
			end
			
			local function alarm(m)
				if m == "" then return end
				rcvpacket.val = {}
				local i,tmp,j = 1
				while i < slen(m) do
					if i+3 > slen(m) then return end
					local flg = sbyte(m,i+1)
					tmp = ""
					for j=0,7 do
						tmp = tmp..(bit.band(flg,2^j)==0 and "0" or "1")
					end
					tmp = tmp.."!"..string.format("%02d%02d",sbyte(m,i+2),sbyte(m,i+3))
					rcvpacket.val[sbyte(m,i)] = tmp
					i = i+4
				end
				return true
			end
			
			local function award(m)
				if slen(m) ~= 12 then return end
				_,rcvpacket.total = lpack.unpack(ssub(m,1,4),">i")
				_,rcvpacket.finish = lpack.unpack(ssub(m,5,8),">i")
				_,rcvpacket.today = lpack.unpack(ssub(m,9,12),">i")
				return true
			end
			
			local function notice(m)
				rcvpacket.val = {}
				local i,tmp,j = 1
				while i < slen(m) do
					if i+4 > slen(m) then return end
					if i+4+sbyte(m,i+4) > slen(m) then return end
					local flg = sbyte(m,i+1)
					tmp = ""
					for j=0,7 do
						tmp = tmp..(bit.band(flg,2^j)==0 and "0" or "1")
					end
					tmp = tmp.."!"..string.format("%02d%02d",sbyte(m,i+2),sbyte(m,i+3)).."!"..common.ucs2betogb2312(ssub(m,i+5,i+4+sbyte(m,i+4)))
					rcvpacket.val[sbyte(m,i)] = tmp
					i = i+5+sbyte(m,i+4)
				end
				return true
			end
			
			local function boolean(m)
				if slen(m) ~= 1 then return end
				rcvpacket.val = (sbyte(m)==1)
				return true
			end
			
			local proc =
			{
				[RPTFREQ] = unsignshort,
				[CALLVOL] = unsignchar,
				[SILTIME] = timesect,
				[FIXMOD] = unsignchar,
				[AWARD] = award,
				[NOTICE] = notice,
				[WHITENUMSWITCH] = unsignchar,
				[DIALSWITCH] = boolean,
			}
			rcvpacket.cmd = sbyte(d)
			if not proc[sbyte(d)] then log.error("protoair.unpack:unknwon setpara",sbyte(d)) return end
			return proc[sbyte(d)](ssub(d,2,-1)) and rcvpacket or nil
		end
	end
	
	local function dial(d)
		if d == "" then return end

		local numcnt,i = sbyte(d)
		if numcnt*6 >= slen(d) then return end
		rcvpacket.num = {}
		for i=1,numcnt do
			local n = unbcd(ssub(d,2+(i-1)*6,7+(i-1)*6))
			if n and slen(n) > 0 then
				table.insert(rcvpacket.num,n)
			end
		end
		return true
	end
	
	local function empty(d)
		return d==""
	end
	
	local function smpset(d)
		if slen(d) > 0 then
			
			local function datetime(m)
				if slen(m) ~= 6 then return end
				rcvpacket.year = 2000+sbyte(m,1)
				rcvpacket.month = sbyte(m,2)
				rcvpacket.day = sbyte(m,3)
				rcvpacket.hour = sbyte(m,4)
				rcvpacket.min = sbyte(m,5)
				rcvpacket.sec = sbyte(m,6)
				return true
			end
			
			local function smscenternum(m)
				if slen(m) == 0 then return end
				rcvpacket.num = unbcd(m)				
				return true
			end
			
			local function simnumrpt(m)
				return slen(m)==0
			end
						
			local proc =
			{
				[DATETIME] = datetime,
				[SMSCENTERNUM] = smscenternum,
				[SIMNUMRPT] = simnumrpt,
			}
			rcvpacket.cmd = sbyte(d)
			if not proc[sbyte(d)] then long.error("protoair.unpack:unknwon smpset",sbyte(d)) return end			
			return proc[sbyte(d)](ssub(d,2,-1)) and rcvpacket or nil
		end
	end
	
	local function qrypara(d)
		if d == "" then return end
		rcvpacket.val = sbyte(d)
		return true
	end	
	
	local function chatrcd(d)
		log.info("protoair.chatrcd",slen(d),sbyte(d,5),sbyte(d,6),sbyte(d,7),sbyte(d,8),sbyte(d,9))
		if slen(d)<10 then return end
		rcvpacket.tm = sbyte(d,1)*2^24+sbyte(d,2)*2^16+sbyte(d,3)*2^8+sbyte(d,4)
		rcvpacket.sn,rcvpacket.total,rcvpacket.cur,rcvpacket.datyp,rcvpacket.tmlen,rcvpacket.dat = sbyte(d,5),sbyte(d,6),sbyte(d,7),sbyte(d,8),sbyte(d,9),ssub(d,10,-1)
		return true
	end
	
	local function updpb(d)
		if slen(d)<9 then return end		
		rcvpacket.cmd,rcvpacket.pbtyp = sbyte(d,1),sbyte(d,2)
		if not (rcvpacket.cmd<=2) then return end
		if not (rcvpacket.pbtyp<=4) then return end
		local tmp,_ = (rcvpacket.pbtyp==2 and (sbyte(d,3)+8) or (rcvpacket.pbtyp==3 and 3 or sbyte(d,3)))
		if slen(d)<(3+tmp) then return end
		if rcvpacket.pbtyp==2 then
			rcvpacket.phone,rcvpacket.pid = unbcdnum(ssub(d,4,3+sbyte(d,3))),unbcd(ssub(d,4+sbyte(d,3),11+sbyte(d,3)))			
		elseif rcvpacket.pbtyp==3 then
			_,rcvpacket.pid = lpack.unpack(ssub(d,3,3+tmp),">i")
			rcvpacket.pid = tostring(rcvpacket.pid)
		else
			rcvpacket.phone = unbcdnum(ssub(d,4,3+tmp))
			rcvpacket.pid = rcvpacket.phone[1]
		end
		if slen(d)<(4+tmp) then return end
		if slen(d)<(4+tmp+sbyte(d,4+tmp)) then return end
		rcvpacket.name = ssub(d,5+tmp,4+tmp+sbyte(d,4+tmp))
		--log.info("protoair.updpb name",common.binstohexs(rcvpacket.name))
		if slen(d)<(5+tmp+sbyte(d,4+tmp)) then return end
		local flg = sbyte(d,5+tmp+sbyte(d,4+tmp))
		--log.info("protoair.updpb flg",flg)
		rcvpacket.icontyp,rcvpacket.iconupd = bit.band(flg,0x01),bit.band(flg,0x02)~=0
		--log.info("protoair.updpb iconid",common.binstohexs(ssub(d,6+tmp+sbyte(d,4+tmp),9+tmp+sbyte(d,4+tmp))))
		if slen(d)~=(9+tmp+sbyte(d,4+tmp)) then return end
		_,rcvpacket.iconid = lpack.unpack(ssub(d,6+tmp+sbyte(d,4+tmp),9+tmp+sbyte(d,4+tmp)),">i")
		rcvpacket.iconid = tostring(rcvpacket.iconid)
		--log.info("protoair.updpb",rcvpacket.cmd,rcvpacket.pbtyp,rcvpacket.pid,rcvpacket.phone,rcvpacket.icontyp,rcvpacket.iconupd,rcvpacket.iconid)
		return true
	end
	
	local function parasync(d)
		if slen(d)<3 then log.error("protoair.parasync len err",slen(d)) return end
		
		local function paraverfnc(val)
			if slen(val)~=4 then log.error("protoair.paraverfnc len err",slen(val)) return end
			_,rcvpacket.paraver = lpack.unpack(val,">i")
			return true
		end
		
		local function synendfnc(val)
			if slen(val)~=0 then log.error("protoair.paraverfnc len err",slen(val)) return end
			rcvpacket.synend = true
			return true
		end
		
		local function pbfnc(val)
			if not rcvpacket.pblist then rcvpacket.pblist={} end
			if val=="" then return true end
			if slen(val)<4 then log.error("protoair.pbfnc len err",slen(val)) return end			
			local item = {}
			item.pbtyp = sbyte(val,1)
			if not (item.pbtyp<=4) then log.error("protoair.pbfnc pbtyp err",item.pbtyp) return end
			local tmp,_ = (item.pbtyp==2 and (sbyte(val,2)+8) or (item.pbtyp==3 and 3 or sbyte(val,2)))
			if slen(val)<(2+tmp) then log.error("protoair.pbfnc len1 err",slen(val)) return end
			if item.pbtyp==2 then				
				item.phone,item.pid = unbcd(ssub(val,3,2+sbyte(val,2))),unbcd(ssub(val,3+sbyte(val,2),10+sbyte(val,2)))
			elseif item.pbtyp==3 then
				_,item.pid = lpack.unpack(ssub(val,2,2+tmp),">i")
				item.pid = tostring(item.pid)
			else
				item.phone = unbcdnum(ssub(val,3,2+tmp))
				item.pid = item.phone[1]
			end
			if slen(val)<(3+tmp) then log.error("protoair.pbfnc len2 err",slen(val),3+tmp) return end
			if slen(val)~=(3+tmp+sbyte(val,3+tmp)) then log.error("protoair.pbfnc len3 err",slen(val),3+tmp+sbyte(val,3+tmp)) return end
			item.name = ssub(val,4+tmp,3+tmp+sbyte(val,3+tmp))
			log.info("protoair.pbfnc name",string.tohex(item.name))
			table.insert(rcvpacket.pblist,item)
			return true
		end
		
		local function silfnc(val)
			if not rcvpacket.sil then rcvpacket.sil = {} end
			if val=="" then return true end

			local i,tmp,j = 1
			while i < slen(val) do
				if i+5 > slen(val) then return end
				local flg = sbyte(val,i+1)
				tmp = ""
				for j=1,7 do
					tmp = tmp..(bit.band(flg,2^j)==0 and "0" or "1")
				end
				tmp = tmp.."!"..string.format("%02d%02d%02d%02d",sbyte(val,i+2),sbyte(val,i+3),sbyte(val,i+4),sbyte(val,i+5))
				rcvpacket.sil[sbyte(val,i)] = tmp
				i = i+6					
			end

			return true
		end
		
		local function awardfnc(m)
			if slen(m) ~= 12 then return end
			_,rcvpacket.awardtotal = lpack.unpack(ssub(m,1,4),">i")
			_,rcvpacket.awardfinish = lpack.unpack(ssub(m,5,8),">i")
			_,rcvpacket.awardtoday = lpack.unpack(ssub(m,9,12),">i")
			return true
		end
		
		local function noticefnc(m)
			if not rcvpacket.notice then rcvpacket.notice = {} end
			if m == "" then return true end			
			local i,tmp,j = 1
			while i < slen(m) do
				if i+4 > slen(m) then return end
				if i+4+sbyte(m,i+4) > slen(m) then return end
				local flg = sbyte(m,i+1)
				tmp = ""
				for j=0,7 do
					tmp = tmp..(bit.band(flg,2^j)==0 and "0" or "1")
				end
				tmp = tmp.."!"..string.format("%02d%02d",sbyte(m,i+2),sbyte(m,i+3)).."!"..common.ucs2betogb2312(ssub(m,i+5,i+4+sbyte(m,i+4)))
				rcvpacket.notice[sbyte(m,i)] = tmp
				i = i+5+sbyte(m,i+4)
			end
			return true
		end
		
		local function empty(m)
			return true
		end
		
		local function whitenumswitchfnc(m)
			if slen(m) ~= 1 then return end
			rcvpacket.whitenumswitch = sbyte(m,1)
			return true
		end
		
		local function dialswitchfnc(m)
			if slen(m) ~= 1 then return end
			rcvpacket.dialswitch = sbyte(m,1)
			return true
		end
		
		local list =
		{
			[0] = paraverfnc,
			[1] = synendfnc,
			[2] = pbfnc,
			[3] = silfnc,
			[4] = awardfnc,
			[5] = noticefnc,
			[6] = empty,
			[7] = empty,
			[8] = whitenumswitchfnc,
			[9] = empty,
			[10] = dialswitchfnc,
		}
		local tmp,id,len=d
		while slen(tmp) > 0 do
			id = sbyte(tmp,1)
			log.info("protoair.parasync id",id,slen(tmp))
			if not list[id] then log.error("protoair.parasync id err") return end
			if slen(tmp)<3 then log.error("protoair.parasync len1 err") return end
			len = sbyte(tmp,2)*256+sbyte(tmp,3)
			if slen(tmp)<3+len then log.error("protoair.parasync len2 err") return end
			if not list[id](ssub(tmp,4,3+len)) then return end
			tmp = ssub(tmp,4+len,-1)
		end
		return true
	end
	
	local function getpararsp(d)
		if slen(d) == 0 then return end
		local typ,_ = sbyte(d)
		if typ==EGG then
			if slen(d) ~= 13 then return end
			_,rcvpacket.awardtotal = lpack.unpack(ssub(d,2,5),">i")
			_,rcvpacket.awardfinish = lpack.unpack(ssub(d,6,9),">i")
			_,rcvpacket.awardtoday = lpack.unpack(ssub(d,10,13),">i")
		end
		rcvpacket.paratyp = typ
		return true
	end
	
	local procer = {
		[SETPARA] = setpara,
		[QRYLOC] = empty,
		[RESET] = empty,
		[MONIT] = dial,
		[POWEROFF] = empty,
		[RESTORE] = empty,
		[SMPSET] = smpset,
		[PARASYNC] = parasync,
		[GETPARARSP] = getpararsp,
		[QRYPARA] = qrypara,
		[RINGON] = empty,
		[CHATRCD] = chatrcd,
		[UPDPB] = updpb,
		[TAKEPHOTO] = empty,
	}
	local id = sbyte(s,1)
	if not procer[id] then log.error("protoair.unpack:unknwon id",id) return end
	rcvpacket.id = id
	log.info("protoair.unpack",id,string.tohex(ssub(s,1,200)))
	return procer[id](ssub(s,2,-1)) and rcvpacket or nil
end

function reget(id)
	get = id
end
